﻿
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Device.Location;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.Phone.Controls.Maps;
using uottawatourguide.Helpers;
using uottawatourguide.Models;
using uottawatourguide.RouteService;



namespace uottawatourguide
{
    public partial class MainPage : INotifyPropertyChanged
    {
        #region Consts
        /// <value>Default map zoom level.</value>
        private const double DefaultZoomLevel = 18.0;

        /// <value>Maximum map zoom level allowed.</value>
        private const double MaxZoomLevel = 21.0;

        /// <value>Minimum map zoom level allowed.</value>
        private const double MinZoomLevel = 18.0;
        #endregion

        #region Fields

        /// <value>Helper for handling mouse (touch) events.</value>
        private readonly TouchBehavior _touchBehavior;

        /// <value>Helper for handling visual states.</value>
        private readonly VisualStates _visualStates;

        /// <value>Provides credentials for the map control.</value>
        private readonly CredentialsProvider _credentialsProvider = new ApplicationIdCredentialsProvider(App.Id);

        /// <value>Default location coordinate.</value>
        private static readonly GeoCoordinate DefaultLocation = new GeoCoordinate(45.42332, -75.68342);//Middle of U of Ottawa

        /// <value>Collection of pushpins available on map.</value>
        private readonly ObservableCollection<PushpinModel> _pushpins = new ObservableCollection<PushpinModel>
        {
            new PushpinModel
            {
                Location = DefaultLocation,
                Icon = new Uri("/Resources/Icons/Pushpins/PushpinLocation.png", UriKind.Relative)
            }
        };

        /// <value>Collection of calculated map routes.</value>
        private readonly ObservableCollection<RouteModel> _routes = new ObservableCollection<RouteModel>();

        /// <value>Collection of calculated map routes itineraries.</value>
        private readonly ObservableCollection<ItineraryItem> _itineraries = new ObservableCollection<ItineraryItem>();

        /// <value>Map zoom level.</value>
        private double _zoom;

        /// <value>Map center coordinate.</value>
        private GeoCoordinate _center;

        //Follows the device locations
        private readonly GeoCoordinateWatcher _geoWatcher;

        #endregion

        #region Properties

        public bool HasDirections
        {
            get { return Itineraries.Count > 0; }
        }

        /// <summary>
        /// Gets the credentials provider for the map control.
        /// </summary>
        public CredentialsProvider CredentialsProvider
        {
            get { return _credentialsProvider; }
        }

        /// <summary>
        /// Gets or sets the map zoom level.
        /// </summary>
        public double Zoom
        {
            get { return _zoom; }
            set
            {
                var coercedZoom = Math.Max(MinZoomLevel, Math.Min(MaxZoomLevel, value));
                if (_zoom != coercedZoom)
                {
                    _zoom = value;
                    NotifyPropertyChanged("Zoom");
                }
            }
        }

        /// <summary>
        /// Gets or sets the map center location coordinate.
        /// </summary>
        public GeoCoordinate Center
        {
            get { return _center; }
            set
            {
                if (_center != value)
                {
                    _center = value;
                    NotifyPropertyChanged("Center");
                }
            }
        }

        /// <summary>
        /// Gets a collection of pushpins.
        /// </summary>
        public ObservableCollection<PushpinModel> Pushpins
        {
            get { return _pushpins; }
        }

        /// <summary>
        /// Gets a collection of routes.
        /// </summary>
        public ObservableCollection<RouteModel> Routes
        {
            get { return _routes; }
        }

        /// <summary>
        /// Gets a collection of route itineraries.
        /// </summary>
        public ObservableCollection<ItineraryItem> Itineraries
        {
            get { return _itineraries; }
        }

        /// <summary>
        /// Gets or sets the route destination location.
        /// </summary>
        public string To { get; set; }

        /// <summary>
        /// Gets or sets the route origin location.
        /// </summary>
        public string From { get; set; }

        #endregion

        #region Tasks

        private void InitializeDefaults()
        {

            Zoom = DefaultZoomLevel;
            Center = DefaultLocation;
            From = "University of Ottawa, ON";
            To = "Ottawa, ON";
        }

        private void updateMap(LinkedList<StopViewModel> stops)
        {
            //Find closest stop to current device location
            double slat, slong, lat, longi;
            lat = _geoWatcher.Position.Location.Latitude;
            longi = _geoWatcher.Position.Location.Longitude;
            slat = stops.First.Value.Latitude;
            slong = stops.First.Value.Longitude;

            double distance = (lat - slat) * (lat - slat) + (longi - slong) * (longi - slong);

            GeoCoordinate closest = new GeoCoordinate();
            GeoCoordinate next = new GeoCoordinate();

            LocationCollection stopLocations = new LocationCollection();
            foreach (var stop in stops)
            {
                slat = stop.Latitude;
                slong = stop.Longitude;
                stopLocations.Add(new Microsoft.Phone.Controls.Maps.Platform.Location() { Latitude = stop.Latitude, Longitude = stop.Longitude });
                double candidateDistance = (lat - slat) * (lat - slat) + (longi - slong) * (longi - slong);

                if (distance > candidateDistance)
                {
                    distance = candidateDistance;

                    closest.Latitude = stop.Latitude;
                    closest.Longitude = stop.Longitude;

                    LinkedListNode<StopViewModel> nextStopNode = CircularBehavior<StopViewModel>.NextOrFirst(new LinkedListNode<StopViewModel>(stop));

                    next.Latitude = nextStopNode.Value.Latitude;
                    next.Longitude = nextStopNode.Value.Longitude;
                }
            }
            //TODO draw Pushpins on the start and end point

            SolidColorBrush routeBrush = new SolidColorBrush(Colors.Blue);
            MapPolygon routePolygon = new MapPolygon();
            routePolygon.Locations = stopLocations;
            routePolygon.Stroke = routeBrush;
            routePolygon.Opacity = 0.65;
            routePolygon.StrokeThickness = 5.0;

            MapLayer polygonLayer = new MapLayer();
            map.Children.Add(polygonLayer);

            polygonLayer.Children.Add(routePolygon);
            var northeast = new Microsoft.Phone.Controls.Maps.Platform.Location() { Latitude = 45.426558, Longitude = -75.675094 };
            var southwest = new Microsoft.Phone.Controls.Maps.Platform.Location() { Latitude = 45.41847, Longitude = -75.688548 };
            LocationRect _locrect = new LocationRect() { Northeast = northeast, Southwest = southwest };
            map.SetView(_locrect);

            foreach (var stop in stopLocations)
            {
                Ellipse point = new Ellipse();
                point.Width = 10;
                point.Height = 10;
                point.Fill = new SolidColorBrush(Colors.Red);
                point.Opacity = 0.65;
                polygonLayer.AddChild(point, stop);
            }

            ////TODO compute route and draw!!!

            //RouteRequest routeRequest = new RouteRequest();
            //routeRequest.Credentials = new Microsoft.Phone.Controls.Maps.Credentials();
            //routeRequest.Credentials.ApplicationId = App.Id;

            //routeRequest.Options = new RouteOptions();
            //routeRequest.Options.RoutePathType = RoutePathType.Points;

            //routeRequest.Waypoints = new ObservableCollection<Waypoint>();


            //foreach (var stop in stops)
            //{
            //    Waypoint wp = new Waypoint();
            //    wp.Description = stop.Title;
            //    wp.Location = new Location() { Latitude = stop.Latitude,Longitude = stop.Longitude};
            //    routeRequest.Waypoints.Add(wp);
            //}

            //Waypoint closeroute = new Waypoint();
            //closeroute.Description = stops.First.Value.Title;
            //closeroute.Location = new Location();
            //closeroute.Location.Latitude = stops.First.Value.Latitude;
            //closeroute.Location.Longitude = stops.First.Value.Longitude;

            //routeRequest.Waypoints.Add(closeroute);

            //RouteServiceClient routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");

        }

        private void ChangeMapMode()
        {
            if (map.Mode is AerialMode)
            {
                map.Mode = new RoadMode();
            }
            else
            {
                map.Mode = new AerialMode(true);
            }
        }

        private void CenterLocation()
        {
            // Center map to default location.
            Center = DefaultLocation;

            // Reset zoom default level.
            Zoom = DefaultZoomLevel;
        }

        private void CenterPushpinsPopup(Point touchPoint)
        {
            // Reposition the pushpins popup to the center of the touch point.
            Canvas.SetTop(PushpinPopup, touchPoint.Y - ListBoxPushpinCatalog.Height / 2);
        }

        private void CreateNewPushpin(object selectedItem, Point point)
        {
            // Translate the map viewport touch point to a geo coordinate.
            GeoCoordinate location;
            map.TryViewportPointToLocation(point, out location);

            // Use the geo coordinate calculated to add a new pushpin,
            // based on the selected pushpin prototype,
            var pushpinPrototype = selectedItem as PushpinModel;
            var pushpin = pushpinPrototype.Clone(location);
            Pushpins.Add(pushpin);
        }

        private void Pushpin_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var pushpin = sender as Pushpin;

            // Center the map on a pushpin when touched.
            Center = pushpin.Location;
        }

        private void CalculateRoute()
        {
            try
            {
                var routeCalculator = new RouteCalculator(
                    CredentialsProvider,
                    To,
                    From,
                    Dispatcher,
                    result =>
                    {
                        // Clear the route collection to have only one route at a time.
                        Routes.Clear();

                        // Clear previous route related itineraries.
                        Itineraries.Clear();

                        // Create a new route based on route calculator result,
                        // and add the new route to the route collection.
                        var routeModel = new RouteModel(result.Result.RoutePath.Points);
                        Routes.Add(routeModel);

                        // Add new route itineraries to the itineraries collection.
                        foreach (var itineraryItem in result.Result.Legs[0].Itinerary)
                        {
                            Itineraries.Add(itineraryItem);
                        }

                        // Set the map to center on the new route.
                        var viewRect = LocationRect.CreateLocationRect(routeModel.Locations);
                        map.SetView(viewRect);

                        ShowDirectionsView();
                    });

                // Display an error message in case of fault.
                routeCalculator.Error += r => MessageBox.Show(r.Reason);

                // Start the route calculation asynchronously.
                routeCalculator.CalculateAsync();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        #region Event Handlers
        private void Device_PositionChanged(object sender, EventArgs e)
        {

        }
        private void ButtonLocation_Click(object sender, EventArgs e)
        {
            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;

            CenterLocation();
        }

        private void ButtonRoute_Click(object sender, EventArgs e)
        {
            // Display or hide route panel.
            if (_visualStates.RouteState == VisualStates.HideRoute)
            {
                _visualStates.RouteState = VisualStates.ShowRoute;
            }
            else
            {
                _visualStates.RouteState = VisualStates.HideRoute;
            }

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void ButtonDirections_Click(object sender, EventArgs e)
        {
            // Display or hide directions panel.
            if (_visualStates.DirectionsState == VisualStates.HideDirections)
            {
                if (HasDirections)
                {
                    _visualStates.DirectionsState = VisualStates.ShowDirections;
                }
            }
            else
            {
                _visualStates.DirectionsState = VisualStates.HideDirections;
            }

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void ButtonMode_Click(object sender, EventArgs e)
        {
            ChangeMapMode();

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void touchBehavior_Tap(object sender, TouchBehaviorEventArgs e)
        {
            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void touchBehavior_Move(object sender, TouchBehaviorEventArgs e)
        {
            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void touchBehavior_Hold(object sender, TouchBehaviorEventArgs e)
        {
            CenterPushpinsPopup(e.TouchPoint);

            // Show pushpins panel.
            _visualStates.PushpinsState = VisualStates.ShowPushpins;
        }

        private void ButtonGo_Click(object sender, RoutedEventArgs e)
        {
            CalculateRoute();

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void ButtonZoomIn_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            Zoom += 1;

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void ButtonZoomOut_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            Zoom -= 1;

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        private void ListBoxPushpinCatalog_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selector = sender as Selector;
            if (selector == null || selector.SelectedItem == null)
            {
                return;
            }

            CreateNewPushpin(selector.SelectedItem, _touchBehavior.LastTouchPoint);

            // Reset the selected item so we can pick it again next time.
            selector.SelectedItem = null;

            // Hide pushpins panel.
            _visualStates.PushpinsState = VisualStates.HidePushpins;
        }

        #endregion

        #region Privates

        /// <summary>
        /// Hides the route view and display the directions view.
        /// </summary>
        private void ShowDirectionsView()
        {
            _visualStates.RouteState = VisualStates.HideRoute;

            if (HasDirections)
            {
                _visualStates.DirectionsState = VisualStates.ShowDirections;
            }
        }

        #endregion

        #region Property Changed

        private void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        #endregion

        #region Visual States
        /// <summary>
        /// An internal helper class for handling MainPage visual state transitions.
        /// </summary>
        private class VisualStates
        {
            #region Predefined Visual States
            // Route States
            public const string ShowRoute = "ShowRoute";
            public const string HideRoute = "HideRoute";

            // Directions States
            public const string ShowDirections = "ShowDirections";
            public const string HideDirections = "HideDirections";

            // Pushpins States
            public const string ShowPushpins = "ShowPushpins";
            public const string HidePushpins = "HidePushpins";
            #endregion

            #region Fields
            private readonly Control _control;
            private string _routeState = VisualStates.HideRoute;
            private string _directionsState = VisualStates.HideDirections;
            private string _pushpinsState = VisualStates.HidePushpins;
            #endregion

            #region Properties
            /// <summary>
            /// Change the route panel visual state.
            /// </summary>
            public string RouteState
            {
                get { return _routeState; }
                set
                {
                    if (_routeState != value)
                    {
                        _routeState = value;
                        VisualStateManager.GoToState(_control, value, true);
                    }
                }
            }

            /// <summary>
            /// Change the directions panel visual state.
            /// </summary>
            public string DirectionsState
            {
                get { return _directionsState; }
                set
                {
                    if (_directionsState != value)
                    {
                        _directionsState = value;
                        VisualStateManager.GoToState(_control, value, true);
                    }
                }
            }

            /// <summary>
            /// Change the pushpins popup visual state.
            /// </summary>
            public string PushpinsState
            {
                get { return _pushpinsState; }
                set
                {
                    if (_pushpinsState != value)
                    {
                        _pushpinsState = value;
                        VisualStateManager.GoToState(_control, value, true);
                    }
                }
            }
            #endregion


            public VisualStates(Control control)
            {
                _control = control;
            }
        }
        #endregion
    }
}